#ifndef SCREENCAPTUREENGINE_H
#define SCREENCAPTUREENGINE_H

#include <QObject>
#include <QWidget>
#include <QLabel>
#include <QPushButton>
#include <QTimer>
#include <queue>
#include <mutex>
#include <condition_variable>
#include <thread>
#include <opencv2/opencv.hpp>
#include <QEventLoop>
#ifdef Q_OS_WINDOWS
#include <windows.h>
#endif

using namespace cv;

class ScrollEventQueue
{
    using Task = std::function<void()>;
public:
    ScrollEventQueue()
    {
        std::thread([&]{
            Run();
        }).detach();
    }

    ~ScrollEventQueue()
    {
        if(m_bRunning)
            m_bRunning = false;
    }

    void Wait()
    {
        if(!m_bFinished)
        {
            m_bRunning = false;
            m_thCv.notify_all();
            if(!m_eventLoop.isRunning())
                m_eventLoop.exec();
        }
    }


    void Push(Task&& task)
    {
        std::unique_lock<std::mutex> lck(m_thMutex);
        m_taskQueue.emplace(task);
        m_thCv.notify_one();
    }

    void Stop()
    {
        m_bRunning = false;
    }

protected:
    void Run()
    {
        while(m_bRunning)
        {
            Task task;
            {
                std::unique_lock<std::mutex> lck(m_thMutex);
                m_thCv.wait(lck, [&]{
                    return !m_bRunning || !m_taskQueue.empty();
                });
                if(!m_bRunning || m_taskQueue.empty())
                {
                    if(m_eventLoop.isRunning())
                        m_eventLoop.exit();
                    m_bFinished = true;
                    return;
                }
                task = std::move_if_noexcept(m_taskQueue.front());
                m_taskQueue.pop();
            }
            task();
        }
        if(m_eventLoop.isRunning())
            m_eventLoop.exit();
        m_bFinished = true;
    }

private:
    QEventLoop m_eventLoop;
    std::queue<Task> m_taskQueue;
    std::mutex m_thMutex;
    std::condition_variable m_thCv;
    std::atomic<bool> m_bFinished = { false };
    std::atomic<bool> m_bRunning = { true };
};


// 放置功能按钮
class ToolBar : public QWidget
{
    Q_OBJECT
public:
    explicit ToolBar(QWidget* parent = nullptr);
    ~ToolBar();

    void Push(QPushButton* pButton);

protected:
    void paintEvent(QPaintEvent* event) override;

private:
    int m_nPos;
};

class CaptureView : public QWidget
{
    Q_OBJECT
    struct CaptureInformation
    {
        short nDelta = 0;
        QPixmap pixmap;
    };

    /**
     * @brief The LastCaptureData struct 此次截图前，上次截图的图片以及拼接的位置
     */
    struct LastCaptureData
    {
        bool bInReverse = false; // 反向
        short delta = 0; // 上一次的滚轮方向
        int y = -1; // 上一次拼接的位置
        QPixmap lastImage; // 上一次的截图
    };

public:
    enum CaptureOrientation // 滚动截图的预览的方位
    {
        Left = 0x128, // 左
        Right, // 右
        Top, // 上
        Bottom, // 下
        Embedded = 0x256, // 开始内嵌
        Embedded_Top, // 内嵌上边(右上), 内嵌默认在右上
        Embedded_Bottom // 内嵌在下边(右下)
    };
    CaptureView(QWidget* parent = nullptr);
    ~CaptureView();
    static const int nCaptureBorder; // 截图区域的边框
    static const QColor borderColor; // 边框颜色
    static CaptureView* gCaptureView;
    const int nROIHeight = 150; // ROI区域最小高度

    const int nScrollPreviewHValue = 350; // 无论哪个方位，截图最大宽度就是350
    const int nScrollPreviewHMinHeight = 400; // 截图预览位于水平方向时，高度最小为400
    const int nScrollPreviewHMaxHeight = 600; // 截图预览位于水平方向时，高度最大为600


    const int nScroolPreviewVEMinHeight = 150; // 截图预览位于上下或者内嵌时，高度最小为150
    const int nScroolPreviewVEMaxHeight = 250; // 截图预览位于上下或者内嵌时，高度最大为250

    const int nScrollPreviewMargin = 15; // 滚动截图预览的外边距
    const int nScrollIntervalBase = 80; // 滚动截图间隔基准参数，单位毫秒
    const int nScrollMaxHeight = 6000; // 滚动截图最大高度
    const int nScrollStep = 25;

    /**
     * @brief IsInCaptureArea 判断指定坐标是否在截图区域内
     * @param x 指定的x坐标
     * @param y 指定的y坐标
     * @return true: 在区域内
     */
    bool IsInCaptureArea(int x, int y) const;

    /**
     * @brief Capture 将截图区域变为图片
     * @param rect 要截图的区域，如果没有指定，默认为m_captureRect
     */
    void Capture(const QRect& rect = QRect());

    /**
     * @brief ScrollCapture 滚动截图
     * @param nDelta 滚轮值，参考下面的`Scrolled`信号
     */
    void ScrollCapture(short nDelta);

    /////////////////////// 滚动截图 ///////////////////////////////////////

    /**
     * @brief StartScrollTimer 开启滚动计数器
     */
    void StartScrollTimer();

    /**
     * @brief IsScrollTimerActive 滚动计数器是否开启
     * @return
     */
    bool IsScrollTimerActive() const;

    /**
     * @brief PreviewOrientation 当前滚动截图预览图的方位
     * @return
     */
    CaptureOrientation PreviewOrientation() const;

    /**
     * @brief ChangePreviewOrientation 改变滚动截图预览图的方位
     */
    void ChangePreviewOrientation(CaptureOrientation ori);

    /**
     * @brief IsContinueScroll 是否可以继续滚动截图
     * @return  true: 可以， false：不行，已到达最大高度
     */
    bool IsContinueScroll() const;

    /**
     * @brief MatchTemplate 模板匹配
     * @param src_gray 原图（灰度图）
     * @param temp 匹配的模板（非灰度）
     * @return true: src中包含temp。false：src中不包含temp。
     */
    bool MatchTemplate(const Mat& src_gray, const Mat& temp);

    //////////////////////////////////////////////////////////////////////////

protected:
    void ProcessScrollEvent(CaptureInformation info);
    bool InstallHook();
    void UninstallHook();
    bool eventFilter(QObject *watched, QEvent *event) override;
    void paintEvent(QPaintEvent* event) override;
    void mousePressEvent(QMouseEvent *event) override;
    void mouseMoveEvent(QMouseEvent *event) override;
    void mouseReleaseEvent(QMouseEvent *event) override;
    void keyPressEvent(QKeyEvent *event) override;

private:

    /**
     * @brief PlaceLabelSize 将m_pLabelSize放置到合适的位置
     * @param ptDiff 当前鼠标坐标减去按下时刻的坐标
     */
    void PlaceLabelSize(const QPointF& ptDiff);

    /**
     * @brief PlaceToolBar 放置ToolBar到合适的位置
     */
    void PlaceToolBar();

    /**
     * @brief PlaceScrollPreview 放置滚动截图的预览
     */
    void PlaceScrollPreview();

    /**
     * @brief CaptureRegionTransform 截图区域rect的转换
     * @param ptDiff
     */
    void CaptureRegionTransform(const QPointF& ptDiff);

private:
    QLabel* m_pLabelSize; // 显示当前截图区域的大小
    ToolBar* m_pToolBar;
    QScreen* m_pScreen;
    bool m_bLPressed; // 左键是否按下
    bool m_bLFinished; // 左键按下并松开，结束区域的决定
    bool m_bScrollCapture; // 是否长截图
#ifdef Q_OS_WINDOWS
    HHOOK m_mouseHookHandler;
#endif
    QRect m_screenRect; // 屏幕区域
    QRect m_captureRect; // 截图的区域
    QPoint m_ptPressed; // 按下时候的坐标
    QPoint m_ptCurrent; // 当前移动的坐标
    QPixmap m_screenImage; // 整个屏幕的截图
    QPixmap m_captureImage; // 当前区域的截图

    /////////////////////// 下面和滚动截图有关 ////////////////////////////

    LastCaptureData m_stLastData;
    QPixmap m_scrollImage; // 滚动截图的图片
    QPixmap m_scrollImageBk; // 滚动截图的图片的副本
    ScrollEventQueue m_seQueue;
    QTimer m_scrollTimer;
    QTimer m_scrollPromptTimer; // 滚动截图里，提示的定时器
    QLabel* m_pLabelPrompt; // 滚动的提示。写个Label就行了。
    int m_nScrollPreviewX = -1; // 滚动截图预览图的x位置
    int m_nScrollPreviewY = -1; // 滚动截图预览图的y位置
    const int m_nPromptDuration = 1500; // 提示持续1500ms
    CaptureOrientation m_enOrientation = Left;

    /////////////////////////////////////////////////////////////////////////
signals:

    /**
     * @brief ShowPrompt 显示提示
     * @param str 要提示的内容
     */
    void ShowPrompt(const QString& str);

    /**
     * @brief EnableScrollTimer 启用/禁用滚动定时器
     */
    void EnableScrollTimer();

    /**
     * @brief CaptureFinished 窗口退出时候发出，代表截屏结束
     * @param b true: 截屏结束, false: 截屏中途退出
     * @param pixmap 若b参数是true，则返回截图的图像信息。否则，返回空图像
     */
    void CaptureFinished(bool b, QPixmap pixmap);

    /**
     * @brief Scrolled 滚动截图时候，用户滚轮滚动了
     * @param nDelta 若值大于0，则是向上滚动。若小于0，则是向下滚动
     */
    void Scrolled(CaptureInformation info);

private:
    void FinishCallback(bool b, const QPixmap& pixmap);
};

class ScreenCaptureEngine : public QObject
{
    Q_OBJECT
public:
    explicit ScreenCaptureEngine(QObject *parent = nullptr);
    ~ScreenCaptureEngine();
    Q_DISABLE_COPY_MOVE(ScreenCaptureEngine)

    /**
     * @brief Start 开始截图
     * @param bHide true: 隐藏当前窗口截图，false：不隐藏当前窗口
     */
    void Start(bool bHide = true);

signals:

    /**
     * @brief CaptureFinished 窗口退出时候发出，代表截屏结束
     * @param b true: 截屏结束, false: 截屏中途退出
     * @param pixmap 若b参数的值是true，则返回截图的图像信息。否则，返回空图像
     */
    void CaptureFinished(bool b, QPixmap pixmap);
};

#endif // SCREENCAPTUREENGINE_H
